//
// Created by HP on 2022/2/1.
//

#include "tmc_camera.h"

// std
#include <cassert>
#include <cmath>
#include <limits>

namespace tmc {

    void TmcCamera::setOrthographicProjection(
            float left, float right,
            float top,  float bottom,
            float near, float far
            ) {
        const float diff_rl = right - left;
        const float diff_bt = bottom - top;
        const float diff_fn = far - near;

        projectionMatrix = glm::mat4 {1.0f};
        projectionMatrix[0][0] = 2.f / diff_rl;
        projectionMatrix[1][1] = 2.f / diff_bt;
        projectionMatrix[2][2] = 1.f / diff_fn;
        projectionMatrix[3][0] = -(right + left) / diff_rl;
        projectionMatrix[3][1] = -(bottom + top) / diff_bt;
        projectionMatrix[3][2] = -2 * near / diff_fn;
    }


    void TmcCamera::setPerspectiveProjection(float fovy, float aspect, float near, float far) {
        assert(glm::abs(aspect - std::numeric_limits<float>::epsilon()) > 0.0f);
        const float tanHalfFovy = std::tan(fovy / 2.f);
        const float diff = far - near;

        projectionMatrix = glm::mat4{0.f};
        projectionMatrix[0][0] = 1.f / (aspect * tanHalfFovy);
        projectionMatrix[1][1] = 1.f / tanHalfFovy;
        projectionMatrix[2][2] = far / diff;
        projectionMatrix[2][3] = 1.f;
        projectionMatrix[3][2] = -(far * near) / diff;
    }

    void TmcCamera::setViewDirection(glm::vec3 position, glm::vec3 direction, glm::vec3 up) {
        // 创建一组正交基底 w、u、v
        const glm::vec3 w{glm::normalize(direction)};
        const glm::vec3 u{glm::normalize(glm::cross(w, up))};
        const glm::vec3 v{glm::cross(w, u)};

        //    旋转            平移
        // ux uy uz 0     1 0 0 -p.x
        // vx vy vz 0     0 1 0 -p.y
        // wx wy wz 0  *  0 0 1 -p.z
        // 0  0  0  1     0 0 0   1
        //
        // 总的来说, 就是讲物体旋转, 然后平移到以相机位置为原点的坐标系中
        viewMatrix = glm::mat4{1.f};
        viewMatrix[0][0] = u.x;
        viewMatrix[1][0] = u.y;
        viewMatrix[2][0] = u.z;
        viewMatrix[0][1] = v.x;
        viewMatrix[1][1] = v.y;
        viewMatrix[2][1] = v.z;
        viewMatrix[0][2] = w.x;
        viewMatrix[1][2] = w.y;
        viewMatrix[2][2] = w.z;
        viewMatrix[3][0] = -glm::dot(u, position);
        viewMatrix[3][1] = -glm::dot(v, position);
        viewMatrix[3][2] = -glm::dot(w, position);
    }

    void TmcCamera::setViewTarget(glm::vec3 position, glm::vec3 target, glm::vec3 up) {
        auto direction = target - position;
        assert(glm::dot(direction, direction) > std::numeric_limits<float>::epsilon() &&
        "direction should not be 0 vec");
        setViewDirection(position, direction, up);
    }

    void TmcCamera::setViewYXZ(glm::vec3 position, glm::vec3 rotation) {
        // 旋转相机
        const float c3 = glm::cos(rotation.z);
        const float s3 = glm::sin(rotation.z);
        const float c2 = glm::cos(rotation.x);
        const float s2 = glm::sin(rotation.x);
        const float c1 = glm::cos(rotation.y);
        const float s1 = glm::sin(rotation.y);
        const glm::vec3 u{(c1 * c3 + s1 * s2 * s3), (c2 * s3), (c1 * s2 * s3 - c3 * s1)};
        const glm::vec3 v{(c3 * s1 * s2 - c1 * s3), (c2 * c3), (c1 * c3 * s2 + s1 * s3)};
        const glm::vec3 w{(c2 * s1), (-s2), (c1 * c2)};
        viewMatrix = glm::mat4{1.f};
        viewMatrix[0][0] = u.x;
        viewMatrix[1][0] = u.y;
        viewMatrix[2][0] = u.z;
        viewMatrix[0][1] = v.x;
        viewMatrix[1][1] = v.y;
        viewMatrix[2][1] = v.z;
        viewMatrix[0][2] = w.x;
        viewMatrix[1][2] = w.y;
        viewMatrix[2][2] = w.z;
        viewMatrix[3][0] = -glm::dot(u, position);
        viewMatrix[3][1] = -glm::dot(v, position);
        viewMatrix[3][2] = -glm::dot(w, position);
    }
}